import numpy as np


def get_xy(data):
    data.insert(0, 'x0', 1)
    cols = data.shape[1]

    x_ = data.iloc[:, 0:cols - 1]
    x = x_.values
    y_ = data.iloc[:, cols - 1:cols]
    y = y_.values

    theta = np.zeros((x.shape[1], y.shape[1]))

    return x, y, theta


# 逻辑函数
def sigmoid(z):
    return 1 / (1 + np.exp(-z))


# 代价函数
def costFunction(x, y, theta):
    a = sigmoid(x @ theta)
    yang = y * np.log(a)
    yin = (1 - y) * np.log(1 - a)
    return - np.sum(yang + yin) / len(x)


# 梯度下降函数
def gradientDescent(x, y, theta, alpha, iteration):
    m = len(x)

    for i in range(iteration):
        a = sigmoid(x @ theta)

        theta = theta - (alpha / m) * x.T @ (a - y)
    return theta


# one-hot编码处理
def one_hot_encoding(raw_y):
    result = []
    for i in raw_y:
        temp = np.zeros(10)
        temp[i - 1] = 1
        result.append(temp)
    return np.array(result)


# 序列化参数
def serialize(theta1, theta2):
    return np.append(theta1.flatten(), theta2.flatten())


# 反序列化参数
def deserialize(theta):
    theta1 = theta[:25 * 401].reshape(25, 401)
    theta2 = theta[25 * 401:].reshape(10, 26)
    return theta1, theta2


# 前向传播
def forward(theta, X):
    theta1, theta2 = deserialize(theta=theta)
    a1 = X

    z2 = a1 @ theta1.T
    a2 = sigmoid(z2)

    a2 = np.insert(a2, 0, values=1, axis=1)
    z3 = a2 @ theta2.T

    h = sigmoid(z3)
    return a1, z2, a2, z3, h


# 无正则的代价函数
def cost(theta, X, y):
    a1, z2, a2, z3, h = forward(theta, X)
    J = -np.sum(y * np.log(h) + (1 - y) * np.log(1 - h)) / 5000
    return J


# 有正则的代价函数
def reg_cost(theta, X, y, l):
    theta1, theta2 = deserialize(theta=theta)
    sum1 = np.sum(np.power(theta1[:, 1:], 2))
    sum2 = np.sum(np.power(theta2[:, 1:], 2))
    reg = (sum1 + sum2) * l / (2 * len(X))
    return reg + cost(theta, X, y)
